package com.alipay.service.impl;

import com.alipay.constants.Constants;
import com.alipay.exception.BusinessException;
import com.alipay.exception.ExpireException;
import com.alipay.exception.OverBalanceException;
import com.alipay.mapper.RedBagMapper;
import com.alipay.mapper.RedBagTransMapper;
import com.alipay.model.annotation.CheckAuth;
import com.alipay.model.dto.TransDto;
import com.alipay.model.entity.RedBagEntity;
import com.alipay.model.entity.RedBagTransEntity;
import com.alipay.model.enums.RedBagStatusEnum;
import com.alipay.model.enums.ResponseCodeEnum;
import com.alipay.model.enums.TransStatusEnum;
import com.alipay.model.vo.*;
import com.alipay.service.RedBagService;
import com.alipay.service.redis.CacheService;
import com.alipay.service.rpc.TransService;
import com.alipay.util.RedBagUtil;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.collections.CollectionUtils;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.dao.DuplicateKeyException;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.math.BigDecimal;
import java.util.*;
import java.util.stream.Collectors;


/**
 * @author Jun
 * @date 2019/3/22
 */
@Service
@Slf4j
public class RedBagServiceImpl implements RedBagService {

    @Autowired
    private CacheService cacheService;

    @Autowired
    private RedBagMapper redBagMapper;

    @Autowired
    private RedBagTransMapper redBagTransMapper;

    @Autowired
    private TransService transService;

    @Override
    @Transactional(rollbackFor = Exception.class)
    @CheckAuth
    public SendRedBagResVo applyRedBag(SendRedBagReqVo sendRedBagReqVo, String userId) {
        // 验证签名
        if (!checkSign()) {
            throw new BusinessException(ResponseCodeEnum.BAD_REQUESTS);
        }
        // 红包入库
        RedBagEntity redBagEntity = new RedBagEntity();
        try {
            BeanUtils.copyProperties(sendRedBagReqVo, redBagEntity);
            redBagEntity.setStatus(RedBagStatusEnum.INIT.getStatus());
            redBagEntity.setRedBagNo(UUID.randomUUID().toString());
            redBagEntity.setUserId(userId);
            redBagMapper.insert(redBagEntity);
        } catch (DuplicateKeyException e) {
            // 唯一编号requestNo防重，保证幂等性
            throw new BusinessException(ResponseCodeEnum.RED_BAG_REPETITION);
        }

        // 分配随机红包金额, 红包最小0.01元；也可以客户端分配服务端校验，以减少服务端的压力
        List<BigDecimal> details = RedBagUtil.getRandomRedBag(redBagEntity.getAmount(), redBagEntity.getCount());

        // 随机红包存入Redis
        cacheService.addRedBags(redBagEntity.getRedBagNo(), details);

        // 异步通知转账系统向用户收款， 用户付款成功后， 转账系统再回调本系统
        transService.noticePay(new TransDto(redBagEntity.getRedBagNo(), userId), Constants.CALLBACK_URL);
        return new SendRedBagResVo(redBagEntity.getRedBagNo());
    }


    @Override
    @Transactional(rollbackFor = Exception.class)
    @CheckAuth
    public DisburseResVo disburse(DisburseReqVo disburseReqVo, String userId) {
        // 验证签名
        if (!checkSign()) {
            throw new BusinessException(ResponseCodeEnum.BAD_REQUESTS);
        }

        // 检查红包状态
        Byte status = redBagMapper.queryStatus(disburseReqVo.getRedBagNo());
        if (status.equals(RedBagStatusEnum.FINISHED.getStatus())) {
            throw new BusinessException(ResponseCodeEnum.RED_BAG_FINISHED);
        } else if (status.equals(RedBagStatusEnum.EXPIRE.getStatus())) {
            throw new BusinessException(ResponseCodeEnum.RED_BAG_EXPIRED);
        } else if (status.equals(RedBagStatusEnum.INIT.getStatus())) {
            throw new BusinessException(ResponseCodeEnum.BAD_REQUESTS);
        }

        // redis 的原子操作可保证程序的并发性，但是需要保证Redis中的数据不会丢失
        BigDecimal value = cacheService.getRedBag(disburseReqVo.getRedBagNo());
        if (value == null) {
            // 红包已抢光
            redBagMapper.updateStatus(disburseReqVo.getRedBagNo(), RedBagStatusEnum.FINISHED.getStatus());
            return null;
        } else {
            // 红包记录入库
            Date createTime = new Date();
            RedBagTransEntity redBagTransEntity = new RedBagTransEntity(
                    disburseReqVo.getRedBagNo(), userId, value, TransStatusEnum.INIT.getStatus(), createTime);
            try {
                redBagTransMapper.insert(redBagTransEntity);
            } catch (DuplicateKeyException e) {
                //userId, redBagNo 联合唯一索引保证幂等性
                throw new BusinessException(ResponseCodeEnum.RED_BAG_REPETITION);
            }

            // 累加红包已抢金额，并再次判断是否过期，是否超过红包金额
            try {
                redBagMapper.incrConsumeAmount(disburseReqVo.getRedBagNo(), value);
            } catch (ExpireException e) {
                // 红包已过期，回滚
                throw new BusinessException(ResponseCodeEnum.RED_BAG_EXPIRED);
            } catch (OverBalanceException e) {
                // 红包金额超出，回滚
                throw new BusinessException(ResponseCodeEnum.RED_BAG_FINISHED);
            }
            // 异步通知转账系统转账给用户
            transService.trans(new TransDto(disburseReqVo.getRedBagNo(), userId));
            DisburseResVo disburseResVo = new DisburseResVo(value, createTime);
            return disburseResVo;
        }
    }

    @Override
    @CheckAuth
    public QueryResVo query(QueryReqVo queryReqVo, String userId) {
        // 验证签名
        if (!checkSign()) {
            throw new BusinessException(ResponseCodeEnum.BAD_REQUESTS);
        }
        QueryResVo queryResVo = new QueryResVo();
        RedBagEntity redBagEntity = redBagMapper.getRedBagEntity(queryReqVo.getRedBagNo());
        BeanUtils.copyProperties(redBagEntity, queryResVo);
        List<RedBagTransEntity> redBagTransEntities = redBagTransMapper.queryRedBagTransInfo(queryReqVo.getRedBagNo());
        if (CollectionUtils.isNotEmpty(redBagTransEntities)) {
            List<QueryResVo.Detail> details = redBagTransEntities.parallelStream().map(redBagTransEntity -> {
                QueryResVo.Detail detail = new QueryResVo.Detail();
                detail.setName("");
                detail.setUrl("");
                detail.setAmount(redBagEntity.getAmount());
                detail.setTransTime(redBagTransEntity.getCreateTime());
                return detail;
            }).collect(Collectors.toList());
            Collections.sort(details, Comparator.comparing(QueryResVo.Detail::getTransTime));
            queryResVo.setDetails(details);
        }
        return queryResVo;
    }

    private boolean checkSign() {
        // TODO 验证签名
        return true;
    }

}
